@cosmjs/launchpad
A client library for the Cosmos SDK 0.37 (cosmoshub-3), 0.38 and 0.39
(Launchpad). See the article
Launchpad — A pre-stargate stable version of the Cosmos SDK
to learn more about launchpad.
Basic usage
The basic usage of the package @cosmjs/launchpad
contains the following:
- Create a wallet
- Sign and broadcast transactions
Create a wallet
For the sake of simplicity we use an in-memory wallet. This is not the safest
way to store production keys. The following demo code is intended for developers
using testnet credentials only. Integrating it into end user facing products
requires serious security review.
If you do not yet have a mnemonic, generate a new wallet with a random mnemonic:
import { Secp256k1HdWallet } from "@cosmjs/launchpad";
const wallet = await Secp256k1HdWallet.generate();
console.log("Mnemonic:", wallet.mnemonic);
const [{ address }] = await wallet.getAccounts();
console.log("Address:", address);
Or import an existing one:
import { Secp256k1HdWallet } from "@cosmjs/launchpad";
const wallet = await Secp256k1HdWallet.fromMnemonic(
"enlist hip relief stomach skate base shallow young switch frequent cry park",
);
const [{ address }] = await wallet.getAccounts();
console.log("Address:", address);
Sign and broadcast transactions
A wallet holds private keys and can use them for signing transactions. To do so
we stick the wallet into a client:
import {
Secp256k1HdWallet,
SigningCosmosClient,
coins,
} from "@cosmjs/launchpad";
const wallet = await Secp256k1HdWallet.generate();
const [{ address }] = await wallet.getAccounts();
console.log("Address:", address);
const lcdApi = "https://…";
const client = new SigningCosmosClient(lcdApi, address, wallet);
const account = await client.getAccount();
console.log("Account:", account);
const recipient = "cosmos1b2340gb2…";
await client.sendTokens(recipient, coins(123, "uatom"));
or use custom message types:
import {
Secp256k1HdWallet,
SigningCosmosClient,
coins,
coin,
MsgDelegate,
} from "@cosmjs/launchpad";
const wallet = await Secp256k1HdWallet.generate();
const [{ address }] = await wallet.getAccounts();
console.log("Address:", address);
const lcdApi = "https://…";
const client = new SigningCosmosClient(lcdApi, address, wallet);
const msg: MsgDelegate = {
type: "cosmos-sdk/MsgDelegate",
value: {
delegator_address: address,
validator_address: "cosmosvaloper1yfkkk04ve8a0sugj4fe6q6zxuvmvza8r3arurr",
amount: coin(300000, "ustake"),
},
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "180000",
};
await client.signAndBroadcast([msg], fee);
Advanced usage
Here you will learn a few things that are slightly more advanced:
- Sign only
- Aggregate signatures from multiple signers
Sign only
You can sign a transaction without broadcasting it:
import {
Secp256k1HdWallet,
SigningCosmosClient,
coins,
coin,
MsgDelegate,
} from "@cosmjs/launchpad";
const wallet = await Secp256k1HdWallet.generate();
const [{ address }] = await wallet.getAccounts();
console.log("Address:", address);
const lcdApi = "https://…";
const client = new SigningCosmosClient(lcdApi, address, wallet);
const msg: MsgDelegate = {
type: "cosmos-sdk/MsgDelegate",
value: {
delegator_address: address,
validator_address: "cosmosvaloper1yfkkk04ve8a0sugj4fe6q6zxuvmvza8r3arurr",
amount: coin(300000, "ustake"),
},
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "180000",
};
let signed = await client.sign([msg], fee);
console.log("Signed transaction:", signed);
const result = await client.broadcastTx(signed);
console.log("Broadcasting result:", result);
Aggregate signatures
In this example we use wallet0
/client0
and wallet1
/client1
which can
live on separate systems:
import {
Secp256k1HdWallet,
SigningCosmosClient,
coins,
coin,
MsgDelegate,
} from "@cosmjs/launchpad";
const wallet0 = await Secp256k1HdWallet.fromMnemonic(mnemonic0);
const [{ address: address0 }] = await wallet.getAccounts();
const client0 = new SigningCosmosClient("https://…", address0, wallet0);
const wallet1 = await Secp256k1HdWallet.fromMnemonic(mnemonic1);
const [{ address: address1 }] = await wallet.getAccounts();
const client1 = new SigningCosmosClient("https://…", address1, wallet1);
const msg1: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: address0,
to_address: "cosmos1b2340gb2…",
amount: coins(1234567, "ucosm"),
},
};
const msg2: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: address1,
to_address: "cosmos1b2340gb2…",
amount: coins(1234567, "ucosm"),
},
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "160000",
};
const memo = "This must be authorized by the two of us";
const signed = await client0.sign([msg1, msg2], fee, memo);
const cosigned = await client1.appendSignature(signed);
const result = await client1.broadcastTx(cosigned);
console.log("Broadcasting result:", result);
Cosmos SDK module support
This client library supports connecting to standard Cosmos SDK modules as well
as custom modules. The modularity has two sides that are handled separately:
- Queries are for reading data from the chain;
- Messages are for writing data to chain as part of the transaction signing.
Query support
@cosmjs/launchpad now has a flexible
LcdClient,
which can be extended with all the standard Cosmos SDK modules in a type-safe
way. With
import { LcdClient } from "@cosmjs/launchpad";
const client = new LcdClient(apiUrl);
const response = await client.nodeInfo();
you only get access to blocks, transaction lists and node info. In order to sign
transactions, you need to setup the auth extension with:
import { LcdClient, setupAuthExtension } from "@cosmjs/launchpad";
const client = LcdClient.withExtensions({ apiUrl }, setupAuthExtension);
const { account_number, sequence } = (await client.auth.account(myAddress))
.result.value;
A full client can use all of the following extensions:
import {
LcdClient,
setupAuthExtension,
setupBankExtension,
setupDistributionExtension,
setupGovExtension,
setupMintExtension,
setupSlashingExtension,
setupStakingExtension,
setupSupplyExtension,
} from "@cosmjs/launchpad";
const client = LcdClient.withExtensions(
{ apiUrl },
setupAuthExtension,
setupBankExtension,
setupDistributionExtension,
setupGovExtension,
setupMintExtension,
setupSlashingExtension,
setupStakingExtension,
setupSupplyExtension,
);
const balances = await client.bank.balances(myAddress);
const distParameters = await client.distribution.parameters();
const proposals = await client.gov.proposals();
const inflation = await client.mint.inflation();
const signingInfos = await client.slashing.signingInfos();
const redelegations = await client.staking.redelegations();
const supply = await client.supply.totalAll();
Messages suppport
Every Amino-JSON compatible message can be used for the
Msg
interface like this:
const voteMsg: Msg = {
type: "cosmos-sdk/MsgVote",
value: {
proposal_id: proposalId,
voter: faucet.address,
option: "Yes",
},
};
This is most flexible since you are not restricted to known messages, but gives
you very little type safety. For improved type safety, we added TypeScript
definitions for all common Cosmos SDK messages:
- MsgSend
- MsgMultiSend
- MsgVerifyInvariant
- MsgSetWithdrawAddress
- MsgWithdrawDelegatorReward
- MsgWithdrawValidatorCommission
- MsgFundCommunityPool
- MsgSubmitEvidence
- MsgSubmitProposal
- MsgVote
- MsgDeposit
- MsgUnjail
- MsgCreateValidator
- MsgEditValidator
- MsgDelegate
- MsgBeginRedelegate
- MsgUndelegate
Those can be signed and broadcast the manual way or by using a higher level
SigningCosmosClient:
import {
coins,
BroadcastTxResult,
MsgSubmitProposal,
OfflineSigner,
SigningCosmosClient,
StdFee,
} from "@cosmjs/launchpad";
async function publishProposal(
apiUrl: string,
signer: OfflineSigner,
): Promise<BroadcastTxResult> {
const [{ address: authorAddress }] = await signer.getAccounts();
const memo = "My first proposal on chain";
const msg: MsgSubmitProposal = {
type: "cosmos-sdk/MsgSubmitProposal",
value: {
content: {
type: "cosmos-sdk/TextProposal",
value: {
description:
"This proposal proposes to test whether this proposal passes",
title: "Test Proposal",
},
},
proposer: authorAddress,
initial_deposit: coins(25000000, "ustake"),
},
};
const fee: StdFee = {
amount: coins(5000000, "ucosm"),
gas: "89000000",
};
const client = new SigningCosmosClient(apiUrl, authorAddress, signer);
return client.signAndBroadcast([msg], fee, memo);
}
Custom modules
Both query and message support is implemented in a decentralized fashion, which
allows applications to add their extensions without changing CosmJS. As an
example we show how to build a client for a blockchain with a wasm module:
import {
MsgExecuteContract,
setupWasmExtension,
} from "@cosmjs/cosmwasm-launchpad";
import {
assertIsBroadcastTxResult,
LcdClient,
makeSignDoc,
setupAuthExtension,
StdFee,
StdTx,
} from "@cosmjs/launchpad";
const client = LcdClient.withExtensions(
{ apiUrl },
setupAuthExtension,
setupWasmExtension,
);
const msg: MsgExecuteContract = {
type: "wasm/MsgExecuteContract",
value: {
sender: myAddress,
contract: contractAddress,
msg: wasmMsg,
sent_funds: [],
},
};
const fee: StdFee = {
amount: coins(5000000, "ucosm"),
gas: "89000000",
};
const memo = "Time for action";
const { account_number, sequence } = (await client.auth.account(myAddress))
.result.value;
const signDoc = makeSignDoc([msg], fee, apiUrl, memo, account_number, sequence);
const { signed, signature } = await signer.sign(myAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(signedTx);
assertIsBroadcastTxResult(result);
Secure key storage
Secp256k1HdWallet
supports securely encrypted serialization/deserialization using Argon2 for key
derivation and XChaCha20Poly1305 for authenticated encryption. It can be used as
easily as:
const wallet = await Secp256k1HdWallet.generate(18);
const serialized = await original.serialize("my password");
const restored = await Secp256k1HdWallet.deserialize(serialized, "my password");
If you want to use really strong KDF parameters in a user interface, you should
offload the KDF execution to a separate thread in order to avoid freezing the
UI. This can be done in the advanced mode:
Session 1 (main thread)
const wallet = await Secp256k1HdWallet.generate(18);
Session 1 (WebWorker)
This operation can now run a couple of seconds without freezing the UI.
import { executeKdf } from "@cosmjs/launchpad";
const strongKdfParams: KdfConfiguration = {
algorithm: "argon2id",
params: {
outputLength: 32,
opsLimit: 5000,
memLimitKib: 15 * 1024,
},
};
const encryptionKey = await executeKdf(password, strongKdfParams);
Session 1 (main thread)
const serialized = await wallet.serializeWithEncryptionKey(
encryptionKey,
anyKdfParams,
);
Session 2 (WebWorker)
import { executeKdf, extractKdfConfiguration } from "@cosmjs/launchpad";
const kdfConfiguration = extractKdfConfiguration(serialized);
const encryptionKey = await executeKdf(password, kdfConfiguration);
Session 2 (main thead)
const restored = await Secp256k1HdWallet.deserializeWithEncryptionKey(
serialized,
encryptionKey,
);
License
This package is part of the cosmjs repository, licensed under the Apache License
2.0 (see NOTICE and
LICENSE).